/*------------------------------------------------------------------------------
 *
 * VDockNode.java - Implements a tree node for a VDock internal tree
 * 
 * January 2009, Forhad Ahmed
 * 
 *------------------------------------------------------------------------------
 */

package ui.dock;

import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.Vector;

import javax.swing.JComponent;

import tree.TreeNode;

public class DockNode extends TreeNode {
    public DockNode(Dock dock, JComponent component) {
        super();

        this.orientation = Dock.DOCK_U;
        this.x = this.y = this.w = this.h = 0;
        this.dock = dock;
        this.component = component;
        this.splitters = new Vector();

        objects++;
    }

    public void finalize() {
        objects--;
        System.out.print("VDockNode - ");
        System.out.println(objects);

    }

    public DockNode getParent() {
        DockNode p = (DockNode) this.parent;
        return p;
    }

    public void consume(DockNode child) {
        int index;
 
        assert (child.parent == this);

        this.type = Dock.DOCK_LEAF;
        this.orientation = Dock.DOCK_U;
        this.component = child.component;
        this.removeChild(child);

        /*
         * We are collapsing the child node by copying into the parent for
         * simplicity's sake.  Since the child is in the dock's 'leaves' list, 
         * we must remove the child and add the new 'child' (the parent, 'this') 
         * to the dock's 'leaves' list
         */
        this.dock.leaves.remove(child);
        this.dock.leaves.add(this);
    }

    public void setBounds(Rectangle r) {
        this.setBounds(r.x, r.y, r.width, r.height);
    }
    
    public void setBoundsLinear2(int x, int y, int w, int h) {
        DockNode node;
        int index, size;
        int dW, dH, cx = 0, cy = 0;
        int cap[], scap = 0;
        double sub[];
        int rsub, rssub = 0, diff = 0;
        
        if (x == this.x && y == this.y && w == this.w && h == this.h) {
            return;
        }
        
        dW = w - this.w;
        dH = h - this.h;
        
        this.x = x; this.y = y;
        
        if (this.type == Dock.DOCK_LEAF) {
            this.w = w; 
            this.h = h;
        } else if (this.type == Dock.DOCK_INODE) {
            size = this.children.size();
            cap = new int[size];
            sub = new double[size];
            
            /*
             * Sweep through the nodes to calculate capacities of the children
             * and the amount to subtract from each child.
             */
            for (index = 0; index < size; index++) {
                node = (DockNode) children.get(index);
                if (this.orientation == Dock.DOCK_H) {
                    if (dH < 0) {
                        cap[index] = node.vCapacity(false);
                    }
                } else if (this.orientation == Dock.DOCK_V) {
                    if (dW < 0) {
                        cap[index] = node.hCapacity(false);
                    }  
                }
                scap += cap[index]; 
            }
            
            for (index = 0; index < size; index++) {
                node = (DockNode) children.get(index);
                if (this.orientation == Dock.DOCK_H) {
                    sub[index] = (double)dH * (double)node.h / (double)this.h;
                } else if (this.orientation == Dock.DOCK_V) {
                    sub[index] = (double)dW * (double)node.w / (double)this.w;
                }
            }
            
            /*
             * If the amount we are shrinking by (dW/H) is greater than the sum
             * of the capacities of the children, then shrink all the children
             * by their capacities, set this.w/h and return
             */
            cx = x; cy = y;
            if (this.orientation == Dock.DOCK_H) {
                if (dH < 0 && -dH > scap) {
                    for (index = 0; index < size; index++) {
                        node = (DockNode) children.get(index);
                        node.setBoundsLinear2(x, cy, w, node.h - cap[index]);
                        cy += node.h;
                    }
                    this.w = w; this.h -= scap;
                    return;
                }
            } else if (this.orientation == Dock.DOCK_V) {
                 if (dW < 0 && -dW > scap) {
                     for (index = 0; index < size; index++) {
                         node = (DockNode) children.get(index);
                         node.setBoundsLinear2(cx, y, node.w - cap[index], h);
                         cx += node.w;
                     }
                     this.w -= scap; this.h = h;
                     return;
                 }
            }
            
            /*
             * Apply all the subs
             */
            cx = x; cy = y;
            for (index = 0; index < size; index++) {
                node = (DockNode) children.get(index);
                diff = 0;
                rsub = (int) Math.round(sub[index]);
                if (this.orientation == Dock.DOCK_H) {
                    rssub += rsub;
                    if (index == size - 1) {
                        diff = (dH - rssub);
                    }
                    node.setBoundsLinear2(x, cy, w, node.h + rsub + diff);
                    cy += node.h;    
                } else if (this.orientation == Dock.DOCK_V) {
                    rssub += rsub;
                    if (index == size - 1) {
                        diff = (dW - rssub);
                    }
                    node.setBoundsLinear2(cx, y, node.w + rsub + diff, h);
                    cx += node.w;
                } 
            }
            
            this.w = w; this.h = h;
            
        }
    
    }

    public void setBoundsLinear(int x, int y, int w, int h) {
        DockNode node, lastnode = null;
        double hfactor = 1, vfactor = 1;
        double cx, cy; /* Running value of x and y */
        double cw, ch; /* Calculated width and height */
        int rcx, rcy, rcw, rch, fx, fy;
        int index, size, diff;

        if (x == this.x && y == this.y && w == this.w && h == this.h) {
            return;
        }

        this.x = x;
        this.y = y;

        if (this.type == Dock.DOCK_LEAF) {
            this.w = w;
            this.h = h;
        } else if (this.type == Dock.DOCK_INODE) {
            hfactor = (double) w / (double) this.w;
            vfactor = (double) h / (double) this.h;
            cx = (double) x;
            cy = (double) y;
            size = this.children.size();
            for (index = 0; index < size; index++) {

                node = (DockNode) this.children.get(index);

                cw = (((double) node.w * (double) hfactor));
                ch = (((double) node.h * (double) vfactor));
                rcw = (int) Math.round(cw);
                rch = (int) Math.round(ch);
                rcx = (int) Math.round(cx);
                rcy = (int) Math.round(cy);
                diff = 0;

                if (this.orientation == Dock.DOCK_H) {
                    fy = (lastnode == null ? y : lastnode.y + lastnode.h);
                    if (index == size - 1) {
                        diff = (y + h) - (fy + rch);
                    }
                    node.setBoundsLinear(x, fy, rcw, rch + diff);
                    cy += ch;
                } else if (this.orientation == Dock.DOCK_V) {
                    fx = (lastnode == null ? x : lastnode.x + lastnode.w); 
                    if (index == size - 1) {
                        diff = (x + w) - (fx + rcw);
                    }
                    node.setBoundsLinear(fx, y, rcw + diff, rch);
                    cx += cw;
                }
                lastnode = node;
            }

            this.w = w; 
            this.h = h;
        }
    }

    public void setBounds(int x, int y, int w, int h) {
        int dx, dy, dw, dh;

        dx = x - this.x;
        dy = y - this.y;
        dw = w - this.w;
        dh = h - this.h;

        if (x == this.x && y == this.y && w == this.w && h == this.h) {
            return;
        }

        if (this.type == Dock.DOCK_LEAF) {
            this.x = x;
            this.y = y;
            this.w = w;
            this.h = h;
        } else if (this.type == Dock.DOCK_INODE) {
            /*
             * Instead of linearly scaling all of this node's children by an
             * hfactor and a vfactor, only increase/decrease the sizes of the
             * leaf children that are adjacent to the splitter (direction of 
             * growth).
             */
            if (dw != 0) {
                if (dx == 0) {
                    adjustWidthAndPosition(dx, dw, DockNode.DOCKNODE_RIGHT);
                } else {
                    adjustWidthAndPosition(dx, dw, DockNode.DOCKNODE_LEFT);
                }
            } else if (dh != 0) {
                if (dy == 0) {
                    adjustHeightAndPosition(dy, dh, DockNode.DOCKNODE_BOTTOM);
                } else {
                    adjustHeightAndPosition(dy, dh, DockNode.DOCKNODE_TOP);
                }
            }
        }
    }

    protected void adjustWidthAndPosition(int dx, int dw, int position) {
        DockNode node;
        int index = 0;

        if (this.type == Dock.DOCK_LEAF) {
            this.x += dx;
            this.w += dw;
        } else {
            if (this.orientation == Dock.DOCK_H) {
                this.x += dx;
                this.w += dw;
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    node.adjustWidthAndPosition(dx, dw, position);
                }
            } else {
                if (position == DockNode.DOCKNODE_LEFT) {
                    index = 0;
                } else if (position == DockNode.DOCKNODE_RIGHT) {
                    index = this.children.size() - 1;
                }
                node = (DockNode) this.children.get(index);
                this.x += dx;
                this.w += dw;
                node.adjustWidthAndPosition(dx, dw, position);
            }

        }
    }

    protected void adjustHeightAndPosition(int dy, int dh, int position) {
        DockNode node;
        int index = 0;

        if (this.type == Dock.DOCK_LEAF) {
            this.y += dy;
            this.h += dh;
        } else {
            if (this.orientation == Dock.DOCK_V) {
                this.y += dy;
                this.h += dh;
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    node.adjustHeightAndPosition(dy, dh, position);
                }
            } else {
                if (position == DockNode.DOCKNODE_TOP) {
                    index = 0;
                } else if (position == DockNode.DOCKNODE_BOTTOM) {
                    index = this.children.size() - 1;
                }
                node = (DockNode) this.children.get(index);
                this.y += dy;
                this.h += dh;
                node.adjustHeightAndPosition(dy, dh, position);
            }

        }
    }

    public Rectangle getBounds() {
        Rectangle r = new Rectangle(this.x, this.y, this.w, this.h);
        return r;
    }

    public int getInverseOrientation() {
        int orientation = 0;

        if (this.orientation == Dock.DOCK_H) {
            orientation = Dock.DOCK_V;
        } else if (this.orientation == Dock.DOCK_V) {
            orientation = Dock.DOCK_H;
        } else {
            assert (false);
        }
        return orientation;
    }

    public int getPointRegion(int x, int y) {
        int region = -1;
        
        int left_right_zone_x = (int) (((float)this.w) * (0.4F));
        int top_bottom_zone_y = (int) (((float)this.h) * (0.4F));
        
        if (y <= top_bottom_zone_y) {
            region = DOCKNODE_TOP;
        } else if (y >= this.h - top_bottom_zone_y) {
            region = DOCKNODE_BOTTOM;
        } else if (x <= left_right_zone_x) {
            region = DOCKNODE_LEFT;
        } else if (x >= this.w - left_right_zone_x) {
            region = DOCKNODE_RIGHT;
        }
        
        return region;
    }

    public int getMinLeafWidth(int min) {
        int rval = min;
        DockNode node;
        int index, temp;
        if (this.type == Dock.DOCK_LEAF) {
            rval = (this.w >= min) ? min : this.w;
        } else {
            for (index = 0; index < this.children.size(); index++) {
                node = (DockNode) this.children.get(index);
                temp = node.getMinLeafWidth(rval);
                if (temp < rval) rval = temp;
            }
        }
        return rval;
    }

    public int getMinLeafHeight(int min) {
        int rval = min;
        DockNode node;
        int index, temp;
        if (this.type == Dock.DOCK_LEAF) {
            rval = (this.h >= min) ? min : this.h;
        } else {
            for (index = 0; index < this.children.size(); index++) {
                node = (DockNode) this.children.get(index);
                temp = node.getMinLeafHeight(rval);
                if (temp < rval) rval = temp;
            }
        }
        return rval;
    }
    
    /*
     * Unlike getMinLeafWidth and height, which looks for minimum of all the 
     * children's bounds, these two functions look for min widths and heights of 
     * the relevant splitter's *boundary* children (prune subtrees algorithm)
     */
    public int minBoundaryLeafWidth(int position, int min) {
        int rval = min, temp;
        int index = 0;
        DockNode node;
        
        if (this.type == Dock.DOCK_LEAF) {
            rval = (this.w >= min) ? min : this.w;
        } else {
            if (this.orientation == Dock.DOCK_H) {
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    temp = node.minBoundaryLeafWidth(position, rval);
                    if (temp < rval) rval = temp;
                }
            } else {
                if (position == DockNode.DOCKNODE_LEFT) {
                    index = 0;
                } else if (position == DockNode.DOCKNODE_RIGHT) {
                    index = this.children.size() - 1;
                }
                
                node = (DockNode) this.children.get(index);
                temp = node.minBoundaryLeafWidth(position, min);
                if (temp < rval) rval = temp;
            }
        }
        return rval;
    }
    
    public int minBoundaryLeafHeight(int position, int min) {
        int rval = min, temp;
        int index = 0;
        DockNode node;
        
        if (this.type == Dock.DOCK_LEAF) {
            rval = (this.h >= min) ? min : this.h;
        } else {
            if (this.orientation == Dock.DOCK_V) {
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    temp = node.minBoundaryLeafHeight(position, rval);
                    if (temp < rval) rval = temp;
                }
            } else {
                if (position == DockNode.DOCKNODE_TOP) {
                    index = 0;
                } else if (position == DockNode.DOCKNODE_BOTTOM) {
                    index = this.children.size() - 1;
                }
                
                node = (DockNode) this.children.get(index);
                temp = node.minBoundaryLeafHeight(position, min);
                if (temp < rval) rval = temp;
            }
        }
        return rval;
    }
    
    /*
     * The hCapacity function returns one of 2 values:
     * - If minimum == true, then returns the total width that the node can be
     *   squeezed to (if all the child leaves are horizontally minimum)
     * - If minimum == false, then returns the total width remaining before all
     *   the leaves are squeezed to minimum.
     */
    public int hCapacity(boolean minimum) {
        DockNode node;
        int index;
        int capacity = 0;
        int min = 0xFFFF;
        int max = 0;
        
        if (this.type == Dock.DOCK_LEAF) {
            if (minimum) {
                capacity = DOCKNODE_MINSIZE;
            } else {
                capacity = this.w - DOCKNODE_MINSIZE;
            }
            
        } else if (this.type == Dock.DOCK_INODE) {
            if (this.orientation == Dock.DOCK_V) {
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    capacity += node.hCapacity(minimum);
                }
            } else if (this.orientation == Dock.DOCK_H) {
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    capacity = node.hCapacity(minimum);
                    if (minimum) {
                        if (capacity > max) max = capacity;
                    } else {
                        if (capacity < min) min = capacity;
                    }
                }
                
                if (minimum) {
                    capacity = max;
                } else {
                    capacity = min;
                }   
            }
        }
        
        return capacity;
    }
    
    /*
     * The vCapacity function returns one of 2 values:
     * - If minimum == true, then returns the total height that the node can be
     *   squeezed to (if all the child leaves are vertically minimum)
     * - If minimum == false, then returns the total height remaining before all
     *   the leaves are squeezed to minimum.
     */
    public int vCapacity(boolean minimum) {
        DockNode node;
        int index;
        int capacity = 0;
        int min = 0xFFFF;
        int max = 0;
        
        if (this.type == Dock.DOCK_LEAF) {
            if (minimum) {
                capacity = DOCKNODE_MINSIZE;
            } else {
                capacity = this.h - DOCKNODE_MINSIZE;
            }
        } else if (this.type == Dock.DOCK_INODE) {
            if (this.orientation == Dock.DOCK_H) {
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    capacity += node.vCapacity(minimum);
                }
            } else if (this.orientation == Dock.DOCK_V) {
                for (index = 0; index < this.children.size(); index++) {
                    node = (DockNode) this.children.get(index);
                    capacity = node.vCapacity(minimum);
                    if (minimum) {
                        if (capacity > max) max = capacity;
                    } else {
                        if (capacity < min) min = capacity;
                    }
                }
                
                if (minimum) {
                    capacity = max;
                } else {
                    capacity = min;
                }
            }
        }
        
        return capacity;
    }

    public void syncAll() {
        this.syncComponentsTree();
        this.syncSplittersTree();
    }

    public void highlight() {
        Graphics g = this.component.getGraphics();
        g.drawLine(0, 0, 100, 100);
    }

    public String toString() {
        String s = "[";

        if (this.type == Dock.DOCK_INODE) {
            s += "INODE - ";
            if (this.orientation == Dock.DOCK_H) {
                s += "HORIZONTAL - ";
            } else {
                s += "VERTICAL - ";
            }
        } else {
            s += "LEAF - ";
        }

        s += "(";
        s += Integer.toString(x) + ", ";
        s += Integer.toString(y) + ", ";
        s += Integer.toString(w) + ", ";
        s += Integer.toString(h);
        s += ") -";
        

        s += " parent: " + (this.parent == null ? "(null)" : "(valid)");
        s += " children: " + Integer.toString(this.children.size());
        s += " splitter: " + Integer.toString(this.splitters.size());
        s += " hcapacit: " + this.hCapacity(true);

        s += "]";

        return s;
    }

    protected void addSplitter(DockNode src, DockNode dst,
                               DockSplitter splitter) {
        int srcIndex, dstIndex;
        assert (src != null && dst != null && splitter != null);
        srcIndex = this.children.indexOf(src);

        splitter.src = src;
        splitter.dst = dst;
        splitter.parent = this;
        this.splitters.insertElementAt(splitter, srcIndex);

        this.dock.add(splitter);

    }

    protected void removeSplitter(int index) {
        DockSplitter splitter;

        assert (index < this.splitters.size());
        splitter = (DockSplitter) this.splitters.get(index);
        splitter.parent = null;
        splitter.src = null;
        splitter.dst = null;
        this.splitters.remove(splitter);

        this.dock.remove(splitter);

    }

    protected void syncComponent() {
        int xoff, yoff, woff, hoff;

        xoff = yoff = dock.padding;
        woff = hoff = 2 * dock.padding;

        if (this.type == TREE_LEAF) {

            if (x == 0) {
                xoff = 0;
                woff = dock.padding;
            }
            if (y == 0) {
                yoff = 0;
                hoff = dock.padding;
            }
            if (x + w == dock.getWidth()) {
                if (x != 0) {
                    woff = dock.padding;
                } else {
                    woff = 0;
                }
            }
            if (y + h == dock.getHeight()) {
                if (y != 0) {
                    hoff = dock.padding;
                } else {
                    hoff = 0;
                }
            }
            
            assert(this.component != null);
            this.component.setLocation(x + xoff, y + yoff);
            this.component.setSize(w - woff, h - hoff);
            this.component.validate();
        }
    }

    protected void syncSplitters() {
        int index, width = dock.splitterWidth;
        DockNode n;
        DockSplitter splitter;

        if (this.type == TREE_INODE) {
            /*
             * Sanity check
             */
            if (this.children.size() > 0) {
                assert (this.children.size() > 1);
                assert (this.splitters.size() == this.children.size() - 1);
            }

            for (index = 0; index < this.splitters.size(); index++) {
                splitter = (DockSplitter) this.splitters.get(index);
                n = (DockNode) this.children.get(index);

                if (this.orientation == Dock.DOCK_V) {
                    splitter.setLocation(n.x + n.w - width / 2, this.y);
                    splitter.setSize(width, this.h);
                } else if (this.orientation == Dock.DOCK_H) {
                    splitter.setLocation(this.x, n.y + n.h - width / 2);
                    splitter.setSize(this.w, width);
                }
            }
        }
    }

    protected void syncComponentsTree() {
        DockNode node;
        int index;

        this.syncComponent();
        for (index = 0; index < this.children.size(); index++) {
            node = (DockNode) this.children.get(index);
            node.syncComponentsTree();
        }
    }

    protected void syncSplittersTree() {
        DockNode node;
        int index;

        this.syncSplitters();
        for (index = 0; index < this.children.size(); index++) {
            node = (DockNode) this.children.get(index);
            node.syncSplittersTree();
        }
    }
    
    /*
     * Enumerations
     */
    public static int DOCKNODE_LEFT = 0;
    public static int DOCKNODE_RIGHT = 1;
    public static int DOCKNODE_TOP = 2;
    public static int DOCKNODE_BOTTOM = 3;
    public static int DOCKNODE_MINSIZE = 40;
    public static int DOCKNODE_MINWIDTHFACTOR = 3;

    /*
     * Private data
     */
    public Dock       dock;
    public JComponent component;
    public Vector     splitters;
    public int        orientation;
    public int        x;
    public int        y;
    public int        w;
    public int        h;


    /*
     * Options
     */
    public boolean    mdi = false;
    public static int objects = 0;
    
}

